Import Data

corrcounts_merge <- readRDS("~/VersionControl/senescence_benchmarking/Data/corrcounts_merge.rds")
metadata_merge <- readRDS("~/VersionControl/senescence_benchmarking/Data/metadata_merge.rds")
SenescenceSignatures <- readRDS("~/VersionControl/senescence_benchmarking/CommonFiles/SenescenceSignatures_divided_newCellAge.RDS")
library(markeR)
library(ggplot2)
library(ggpubr)
library(edgeR)
Loading required package: limma
?markeR

Scores

?CalculateScores
ℹ Rendering development documentation for "CalculateScores"

df_logmedian <- CalculateScores(data = corrcounts_merge, metadata = metadata_merge, method = "logmedian", gene_sets = SenescenceSignatures)

senescence_triggers_colors <- c(
  "none" = "#E57373",  # Soft red  
  "Radiation" = "#BDBDBD",  # Medium gray  
  "DNA damage" = "#64B5F6",  # Brighter blue  
  "Telomere shortening" = "#4FC3F7",  # Vivid sky blue  
  "DNA demethylation" = "#BA68C8",  # Rich lavender  
  "Oxidative stress" = "#FDD835",  # Strong yellow  
  "Conditioned Medium" = "#F2994A",  # Warm orange  
  "Oncogene" = "#81C784",  # Medium green  
  "Lipid Accumulation" = "#E57373",  # Coral  
  "Calcium influx" = "#26A69A",  # Deep teal  
  "Plasma membrane dysruption" = "#D32F2F",  # Strong salmon  
  "OSKM factors" = "#FFB74D",  # Bright peach  
  "YAP KO" = "#9575CD"  # Deep pastel purple  
)

cellTypes_colors <- c(
  "Fibroblast" = "#FF6961",   # Strong Pastel Red  
  "Keratinocyte" = "#FFB347", # Strong Pastel Orange  
  "Melanocyte" = "#FFD700",   # Strong Pastel Yellow  
  "Endothelial" = "#77DD77",  # Strong Pastel Green  
  "Neuronal" = "#779ECB",     # Strong Pastel Blue  
  "Mesenchymal" = "#C27BA0"   # Strong Pastel Purple  
)

cond_cohend <- list(A=c("Senescent"), # if no variable is defined, will be the first that appears in the ggplot
                    B=c("Proliferative","Quiescent"))

PlotScores(ResultsList = df_logmedian, ColorVariable = "CellType", GroupingVariable="Condition",  method ="logmedian", ColorValues = cellTypes_colors, ConnectGroups=TRUE, ncol = 6, nrow = 2, widthTitle=20, y_limits = NULL, legend_nrow = 2,xlab=NULL, cond_cohend = cond_cohend)

wrap_title_aux <- function(title, width = 30) {
  if (nchar(title) <= width) {
    return(title)  # No need to wrap if it fits
  }

  wrapped_title <- ""
  while (nchar(title) > width) {
    # Find positions of capital letters and symbols near the wrap point
    capital_pos <- gregexpr("[A-Z]", title)[[1]]
    symbol_pos <- gregexpr("(_|-|:)", title)[[1]]

    # Check for symbol breaks within the last few characters (width - 5 to width)
    valid_symbol_breaks <- symbol_pos[symbol_pos >= (width - 5) & symbol_pos <= width]

    if (length(valid_symbol_breaks) > 0) {
      # If a suitable symbol is found, break at the first valid symbol
      break_at <- valid_symbol_breaks[1]
    } else {
      # If no suitable symbol, look for capital letters within the same range
      valid_capital_breaks <- capital_pos[capital_pos >= (width - 5) & capital_pos <= width]

      if (length(valid_capital_breaks) > 0) {
        # If a capital letter is found, break just before the capital letter
        break_at <- valid_capital_breaks[1] - 1
      } else {
        # If no suitable symbol or capital letter, break at width
        break_at <- width
      }
    }

    # Append the wrapped line
    wrapped_title <- paste0(wrapped_title, substr(title, 1, break_at), "\n")

    # Update title with the remaining text after the break
    title <- substr(title, break_at + 1, nchar(title))
  }

  # Add the remaining part of the title
  wrapped_title <- paste0(wrapped_title, title)

  return(wrapped_title)
}

Debugging

Individual Genes

  • PCA with genes from signatures only

Violin Expression Plots

IndividualGenes_Violins(data = corrcounts_merge, metadata = metadata_merge, genes = c("CDKN1A", "CDKN2A", "GLB1","TP53","CCL2"), GroupingVariable = "Condition", plot=T, ncol=NULL, nrow=2, divide="CellType", invert_divide=FALSE,ColorValues=senescence_triggers_colors, pointSize=2, ColorVariable="SenescentType", title="Senescence", widthTitle=16,y_limits = NULL,legend_nrow=4, xlab="Condition",colorlab="") 
Using gene as id variables

Correlation Heatmap

CorrelationHeatmap(data=corrcounts_merge, 
                   metadata = metadata_merge, 
                   genes=c("CDKN1A", "CDKN2A", "GLB1","TP53","CCL2"), 
                   separate.by = "Condition", 
                   method = "pearson",  
                   colorlist = list(low = "#3F4193", mid = "#F9F4AE", high = "#B44141"),
                   limits_colorscale = c(-1,0,1), 
                   widthTitle = 16, 
                   title = "test", 
                   cluster_rows = TRUE, 
                   cluster_columns = TRUE,  
                   detailedresults = FALSE, 
                   legend_position="right",
                   titlesize=20)
Warning: Heatmap/annotation names are duplicated: pearson's coefficient
Warning: Heatmap/annotation names are duplicated: pearson's coefficient, pearson's coefficient
Warning: `legend_height` you specified is too small, use the default minimal height.
Warning: `legend_height` you specified is too small, use the default minimal height.
Warning: `legend_height` you specified is too small, use the default minimal height.

Expression Heatmaps

ROC/AUC

Cohen’s d

PCA with genes from signature only


#' @importFrom edgeR DGEList
#' @importFrom stats prcomp
#' @import ggplot2
#' @importFrom ggpubr ggarrange

plotPCA <- function(data, metadata, genes=NULL, scale=FALSE, center=TRUE, PCs=list(c(1,2)), ColorVariable=NULL,ColorValues=NULL,pointSize=5,legend_nrow=2, legend_position=c("bottom","top","right","left"),ncol=NULL, nrow=NULL){
  
  legend_position <- match.arg(legend_position)
  
  if (is.null(genes)){
    genes <-  row.names(data)
  }
  
  data <- data[row.names(data) %in% genes, , drop=F]
  
  if (!nrow(data)>1) stop(paste0("Error: Number of genes should be >1; In your data you have only found the gene ",genes))
  
  # Ensure metadata matches sample order if provided
  if (!is.null(metadata)) {
    colnames(metadata)[1] <- "Sample"
    rownames(metadata) <- metadata$Sample
    metadata <- metadata[colnames(data), , drop = FALSE]
    y <- edgeR::DGEList( log2(data+1), samples= metadata)  
  } else {
    y <- edgeR::DGEList( log2(data+1))  
  }
  

  
  nPCs <- max(unlist(PCs)) # get the maximum number of PC based on the user's choice
    
  PCAdata <- stats::prcomp(t(y$counts), scale=scale, center=center)
  PCAcounts <- PCAdata$x
  PCAcounts <- as.data.frame(PCAcounts)
  
  if (nPCs > ncol(PCAcounts)) stop("Error: Number of genes too low for number of chosen PCs. Please reduce number of PCs.")
      
  PCAcounts <-  cbind(PCAcounts[,1:nPCs],y$samples) 
  
  pltList <- list()
  
  for (pc in PCs){
    pc <- unlist(pc)
    ev = PCAdata$sdev^2
    pc_x <- round(100*ev[pc[1]]/sum(ev),2) 
    pc_y <- round(100*ev[pc[2]]/sum(ev),2) 
    
    plt <- ggplot2::ggplot(PCAcounts, ggplot2::aes_string(y = paste0("PC",pc[1]), x =  paste0("PC",pc[2])))
    
    
    # Add jittered points, optionally colored by ColorVariable.Default: Brewer Pallette "Paired"
    if (!is.null(ColorVariable)) {
      plt <- plt + ggplot2::geom_point(ggplot2::aes_string(fill = ColorVariable), size = pointSize, alpha = 0.5, shape=21, color="black")
    } else {
      plt <- plt + ggplot2::geom_point(size = pointSize, alpha = 0.5, shape=21, color="black", fill="#D8D8D8")  
    }
    
    # If ColorValues is provided, use a manual color scale; otherwise, if ColorVariable is provided,
    # use a default brewer palette.
    if (!is.null(ColorValues)) {
      plt <- plt + ggplot2::scale_fill_manual(values = ColorValues)
    } else if (!is.null(ColorVariable)) {
      plt <- plt + ggplot2::scale_fill_brewer(palette = "Paired")
    } 
    
    # Add axis labels (including variance)
    
    xlab <- paste0("PC",pc[1],": ",pc_x,"% variance")
    ylab <- paste0("PC",pc[2],": ",pc_y,"% variance")
    titleplot <- paste0("PC",pc[1], " vs PC",pc[2])
    
    plt <- plt + ggplot2::labs(fill = "", x = xlab, y = ylab, title=titleplot)
    
    # Change theme
    plt <- plt +
      ggplot2::theme_bw()+
    ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, hjust=1),
          plot.title = ggplot2::element_text(hjust = 0.5),
          legend.position = "bottom")
    
    # Adjust legend rows if legend_nrow is specified
    if (!is.null(legend_nrow)) {
      plt <- plt + ggplot2::guides(fill = ggplot2::guide_legend(nrow = legend_nrow, position = legend_position))
    }
    
    # Add reference lines
    plt <- plt +
      ggplot2::geom_vline(xintercept=0, linetype="dotted") + 
      ggplot2::geom_hline(yintercept=0, linetype="dotted")  
    
    
    pltList <- c(pltList, list(plt))
    
    
    
  }
  
  n <- length(pltList)
  
  if(n==1){
    plt <- pltList[[1]]
  } else {
    
    # Determine grid layout
    if (is.null(ncol) && is.null(nrow)) {
      
      ncol <- ceiling(sqrt(n))
      nrow <- ceiling(n / ncol)
      
    } else if (is.null(ncol)){
      
      ncol <- ceiling(n / nrow)
      
    } else if (is.null(nrow)){
      
      nrow <- ceiling(n / ncol)
      
    }
    
    if (!is.null(ColorVariable)) {
      plt <- ggpubr::ggarrange(plotlist = pltList, ncol = ncol, nrow = nrow, common.legend = TRUE, align = "h")
    } else {
      plt <- ggpubr::ggarrange(plotlist = pltList, ncol = ncol, nrow = nrow, align = "h")
    }
    
    
    
  }
  
  print(plt)
  
  invisible(list(plt = plt,
              data = PCAcounts))
  
}
LS0tCnRpdGxlOiAiRGVidWdnaW5nIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIEltcG9ydCBEYXRhCgpgYGB7cn0KY29ycmNvdW50c19tZXJnZSA8LSByZWFkUkRTKCJ+L1ZlcnNpb25Db250cm9sL3NlbmVzY2VuY2VfYmVuY2htYXJraW5nL0RhdGEvY29ycmNvdW50c19tZXJnZS5yZHMiKQptZXRhZGF0YV9tZXJnZSA8LSByZWFkUkRTKCJ+L1ZlcnNpb25Db250cm9sL3NlbmVzY2VuY2VfYmVuY2htYXJraW5nL0RhdGEvbWV0YWRhdGFfbWVyZ2UucmRzIikKU2VuZXNjZW5jZVNpZ25hdHVyZXMgPC0gcmVhZFJEUygifi9WZXJzaW9uQ29udHJvbC9zZW5lc2NlbmNlX2JlbmNobWFya2luZy9Db21tb25GaWxlcy9TZW5lc2NlbmNlU2lnbmF0dXJlc19kaXZpZGVkX25ld0NlbGxBZ2UuUkRTIikKYGBgCgpgYGB7cn0KbGlicmFyeShtYXJrZVIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ3B1YnIpCmxpYnJhcnkoZWRnZVIpCj9tYXJrZVIKYGBgCgojIFNjb3JlcyAKCmBgYHtyfQo/Q2FsY3VsYXRlU2NvcmVzCmBgYAoKYGBge3IgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTZ9CmRmX3NzR1NFQSA8LSBDYWxjdWxhdGVTY29yZXMoZGF0YSA9IGNvcnJjb3VudHNfbWVyZ2UsIG1ldGFkYXRhID0gbWV0YWRhdGFfbWVyZ2UsIG1ldGhvZCA9ICJzc0dTRUEiLCBnZW5lX3NldHMgPSBTZW5lc2NlbmNlU2lnbmF0dXJlcykKCnNlbmVzY2VuY2VfdHJpZ2dlcnNfY29sb3JzIDwtIGMoCiAgIm5vbmUiID0gIiNFNTczNzMiLCAgIyBTb2Z0IHJlZCAgCiAgIlJhZGlhdGlvbiIgPSAiI0JEQkRCRCIsICAjIE1lZGl1bSBncmF5ICAKICAiRE5BIGRhbWFnZSIgPSAiIzY0QjVGNiIsICAjIEJyaWdodGVyIGJsdWUgIAogICJUZWxvbWVyZSBzaG9ydGVuaW5nIiA9ICIjNEZDM0Y3IiwgICMgVml2aWQgc2t5IGJsdWUgIAogICJETkEgZGVtZXRoeWxhdGlvbiIgPSAiI0JBNjhDOCIsICAjIFJpY2ggbGF2ZW5kZXIgIAogICJPeGlkYXRpdmUgc3RyZXNzIiA9ICIjRkREODM1IiwgICMgU3Ryb25nIHllbGxvdyAgCiAgIkNvbmRpdGlvbmVkIE1lZGl1bSIgPSAiI0YyOTk0QSIsICAjIFdhcm0gb3JhbmdlICAKICAiT25jb2dlbmUiID0gIiM4MUM3ODQiLCAgIyBNZWRpdW0gZ3JlZW4gIAogICJMaXBpZCBBY2N1bXVsYXRpb24iID0gIiNFNTczNzMiLCAgIyBDb3JhbCAgCiAgIkNhbGNpdW0gaW5mbHV4IiA9ICIjMjZBNjlBIiwgICMgRGVlcCB0ZWFsICAKICAiUGxhc21hIG1lbWJyYW5lIGR5c3J1cHRpb24iID0gIiNEMzJGMkYiLCAgIyBTdHJvbmcgc2FsbW9uICAKICAiT1NLTSBmYWN0b3JzIiA9ICIjRkZCNzREIiwgICMgQnJpZ2h0IHBlYWNoICAKICAiWUFQIEtPIiA9ICIjOTU3NUNEIiAgIyBEZWVwIHBhc3RlbCBwdXJwbGUgIAopCgpjZWxsVHlwZXNfY29sb3JzIDwtIGMoCiAgIkZpYnJvYmxhc3QiID0gIiNGRjY5NjEiLCAgICMgU3Ryb25nIFBhc3RlbCBSZWQgIAogICJLZXJhdGlub2N5dGUiID0gIiNGRkIzNDciLCAjIFN0cm9uZyBQYXN0ZWwgT3JhbmdlICAKICAiTWVsYW5vY3l0ZSIgPSAiI0ZGRDcwMCIsICAgIyBTdHJvbmcgUGFzdGVsIFllbGxvdyAgCiAgIkVuZG90aGVsaWFsIiA9ICIjNzdERDc3IiwgICMgU3Ryb25nIFBhc3RlbCBHcmVlbiAgCiAgIk5ldXJvbmFsIiA9ICIjNzc5RUNCIiwgICAgICMgU3Ryb25nIFBhc3RlbCBCbHVlICAKICAiTWVzZW5jaHltYWwiID0gIiNDMjdCQTAiICAgIyBTdHJvbmcgUGFzdGVsIFB1cnBsZSAgCikKCmNvbmRfY29oZW5kIDwtIGxpc3QoQT1jKCJTZW5lc2NlbnQiKSwgIyBpZiBubyB2YXJpYWJsZSBpcyBkZWZpbmVkLCB3aWxsIGJlIHRoZSBmaXJzdCB0aGF0IGFwcGVhcnMgaW4gdGhlIGdncGxvdAogICAgICAgICAgICAgICAgICAgIEI9YygiUHJvbGlmZXJhdGl2ZSIsIlF1aWVzY2VudCIpKQoKUGxvdFNjb3JlcyhSZXN1bHRzTGlzdCA9IGRmX3NzR1NFQSwgQ29sb3JWYXJpYWJsZSA9ICJDZWxsVHlwZSIsIEdyb3VwaW5nVmFyaWFibGU9IkNvbmRpdGlvbiIsICBtZXRob2QgPSJzc0dTRUEiLCBDb2xvclZhbHVlcyA9IGNlbGxUeXBlc19jb2xvcnMsIENvbm5lY3RHcm91cHM9VFJVRSwgbmNvbCA9IDYsIG5yb3cgPSAyLCB3aWR0aFRpdGxlPTIwLCB5X2xpbWl0cyA9IE5VTEwsIGxlZ2VuZF9ucm93ID0gMixjb25kX2NvaGVuZD1jb25kX2NvaGVuZCkKCmBgYAoKYGBge3IgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTZ9CmRmX2xvZ21lZGlhbiA8LSBDYWxjdWxhdGVTY29yZXMoZGF0YSA9IGNvcnJjb3VudHNfbWVyZ2UsIG1ldGFkYXRhID0gbWV0YWRhdGFfbWVyZ2UsIG1ldGhvZCA9ICJsb2dtZWRpYW4iLCBnZW5lX3NldHMgPSBTZW5lc2NlbmNlU2lnbmF0dXJlcykKCnNlbmVzY2VuY2VfdHJpZ2dlcnNfY29sb3JzIDwtIGMoCiAgIm5vbmUiID0gIiNFNTczNzMiLCAgIyBTb2Z0IHJlZCAgCiAgIlJhZGlhdGlvbiIgPSAiI0JEQkRCRCIsICAjIE1lZGl1bSBncmF5ICAKICAiRE5BIGRhbWFnZSIgPSAiIzY0QjVGNiIsICAjIEJyaWdodGVyIGJsdWUgIAogICJUZWxvbWVyZSBzaG9ydGVuaW5nIiA9ICIjNEZDM0Y3IiwgICMgVml2aWQgc2t5IGJsdWUgIAogICJETkEgZGVtZXRoeWxhdGlvbiIgPSAiI0JBNjhDOCIsICAjIFJpY2ggbGF2ZW5kZXIgIAogICJPeGlkYXRpdmUgc3RyZXNzIiA9ICIjRkREODM1IiwgICMgU3Ryb25nIHllbGxvdyAgCiAgIkNvbmRpdGlvbmVkIE1lZGl1bSIgPSAiI0YyOTk0QSIsICAjIFdhcm0gb3JhbmdlICAKICAiT25jb2dlbmUiID0gIiM4MUM3ODQiLCAgIyBNZWRpdW0gZ3JlZW4gIAogICJMaXBpZCBBY2N1bXVsYXRpb24iID0gIiNFNTczNzMiLCAgIyBDb3JhbCAgCiAgIkNhbGNpdW0gaW5mbHV4IiA9ICIjMjZBNjlBIiwgICMgRGVlcCB0ZWFsICAKICAiUGxhc21hIG1lbWJyYW5lIGR5c3J1cHRpb24iID0gIiNEMzJGMkYiLCAgIyBTdHJvbmcgc2FsbW9uICAKICAiT1NLTSBmYWN0b3JzIiA9ICIjRkZCNzREIiwgICMgQnJpZ2h0IHBlYWNoICAKICAiWUFQIEtPIiA9ICIjOTU3NUNEIiAgIyBEZWVwIHBhc3RlbCBwdXJwbGUgIAopCgpjZWxsVHlwZXNfY29sb3JzIDwtIGMoCiAgIkZpYnJvYmxhc3QiID0gIiNGRjY5NjEiLCAgICMgU3Ryb25nIFBhc3RlbCBSZWQgIAogICJLZXJhdGlub2N5dGUiID0gIiNGRkIzNDciLCAjIFN0cm9uZyBQYXN0ZWwgT3JhbmdlICAKICAiTWVsYW5vY3l0ZSIgPSAiI0ZGRDcwMCIsICAgIyBTdHJvbmcgUGFzdGVsIFllbGxvdyAgCiAgIkVuZG90aGVsaWFsIiA9ICIjNzdERDc3IiwgICMgU3Ryb25nIFBhc3RlbCBHcmVlbiAgCiAgIk5ldXJvbmFsIiA9ICIjNzc5RUNCIiwgICAgICMgU3Ryb25nIFBhc3RlbCBCbHVlICAKICAiTWVzZW5jaHltYWwiID0gIiNDMjdCQTAiICAgIyBTdHJvbmcgUGFzdGVsIFB1cnBsZSAgCikKCmNvbmRfY29oZW5kIDwtIGxpc3QoQT1jKCJTZW5lc2NlbnQiKSwgIyBpZiBubyB2YXJpYWJsZSBpcyBkZWZpbmVkLCB3aWxsIGJlIHRoZSBmaXJzdCB0aGF0IGFwcGVhcnMgaW4gdGhlIGdncGxvdAogICAgICAgICAgICAgICAgICAgIEI9YygiUHJvbGlmZXJhdGl2ZSIsIlF1aWVzY2VudCIpKQoKUGxvdFNjb3JlcyhSZXN1bHRzTGlzdCA9IGRmX2xvZ21lZGlhbiwgQ29sb3JWYXJpYWJsZSA9ICJDZWxsVHlwZSIsIEdyb3VwaW5nVmFyaWFibGU9IkNvbmRpdGlvbiIsICBtZXRob2QgPSJsb2dtZWRpYW4iLCBDb2xvclZhbHVlcyA9IGNlbGxUeXBlc19jb2xvcnMsIENvbm5lY3RHcm91cHM9VFJVRSwgbmNvbCA9IDYsIG5yb3cgPSAyLCB3aWR0aFRpdGxlPTIwLCB5X2xpbWl0cyA9IE5VTEwsIGxlZ2VuZF9ucm93ID0gMix4bGFiPU5VTEwsIGNvbmRfY29oZW5kID0gY29uZF9jb2hlbmQpCgpgYGAKCmBgYHtyfQp3cmFwX3RpdGxlX2F1eCA8LSBmdW5jdGlvbih0aXRsZSwgd2lkdGggPSAzMCkgewogIGlmIChuY2hhcih0aXRsZSkgPD0gd2lkdGgpIHsKICAgIHJldHVybih0aXRsZSkgICMgTm8gbmVlZCB0byB3cmFwIGlmIGl0IGZpdHMKICB9CiAgCiAgd3JhcHBlZF90aXRsZSA8LSAiIgogIHdoaWxlIChuY2hhcih0aXRsZSkgPiB3aWR0aCkgewogICAgIyBGaW5kIHBvc2l0aW9ucyBvZiBjYXBpdGFsIGxldHRlcnMgYW5kIHN5bWJvbHMgbmVhciB0aGUgd3JhcCBwb2ludAogICAgY2FwaXRhbF9wb3MgPC0gZ3JlZ2V4cHIoIltBLVpdIiwgdGl0bGUpW1sxXV0KICAgIHN5bWJvbF9wb3MgPC0gZ3JlZ2V4cHIoIihffC18OikiLCB0aXRsZSlbWzFdXQogICAgCiAgICAjIENoZWNrIGZvciBzeW1ib2wgYnJlYWtzIHdpdGhpbiB0aGUgbGFzdCBmZXcgY2hhcmFjdGVycyAod2lkdGggLSA1IHRvIHdpZHRoKQogICAgdmFsaWRfc3ltYm9sX2JyZWFrcyA8LSBzeW1ib2xfcG9zW3N5bWJvbF9wb3MgPj0gKHdpZHRoIC0gNSkgJiBzeW1ib2xfcG9zIDw9IHdpZHRoXQogICAgCiAgICBpZiAobGVuZ3RoKHZhbGlkX3N5bWJvbF9icmVha3MpID4gMCkgewogICAgICAjIElmIGEgc3VpdGFibGUgc3ltYm9sIGlzIGZvdW5kLCBicmVhayBhdCB0aGUgZmlyc3QgdmFsaWQgc3ltYm9sCiAgICAgIGJyZWFrX2F0IDwtIHZhbGlkX3N5bWJvbF9icmVha3NbMV0KICAgIH0gZWxzZSB7CiAgICAgICMgSWYgbm8gc3VpdGFibGUgc3ltYm9sLCBsb29rIGZvciBjYXBpdGFsIGxldHRlcnMgd2l0aGluIHRoZSBzYW1lIHJhbmdlCiAgICAgIHZhbGlkX2NhcGl0YWxfYnJlYWtzIDwtIGNhcGl0YWxfcG9zW2NhcGl0YWxfcG9zID49ICh3aWR0aCAtIDUpICYgY2FwaXRhbF9wb3MgPD0gd2lkdGhdCiAgICAgIAogICAgICBpZiAobGVuZ3RoKHZhbGlkX2NhcGl0YWxfYnJlYWtzKSA+IDApIHsKICAgICAgICAjIElmIGEgY2FwaXRhbCBsZXR0ZXIgaXMgZm91bmQsIGJyZWFrIGp1c3QgYmVmb3JlIHRoZSBjYXBpdGFsIGxldHRlcgogICAgICAgIGJyZWFrX2F0IDwtIHZhbGlkX2NhcGl0YWxfYnJlYWtzWzFdIC0gMQogICAgICB9IGVsc2UgewogICAgICAgICMgSWYgbm8gc3VpdGFibGUgc3ltYm9sIG9yIGNhcGl0YWwgbGV0dGVyLCBicmVhayBhdCB3aWR0aAogICAgICAgIGJyZWFrX2F0IDwtIHdpZHRoCiAgICAgIH0KICAgIH0KICAgIAogICAgIyBBcHBlbmQgdGhlIHdyYXBwZWQgbGluZQogICAgd3JhcHBlZF90aXRsZSA8LSBwYXN0ZTAod3JhcHBlZF90aXRsZSwgc3Vic3RyKHRpdGxlLCAxLCBicmVha19hdCksICJcbiIpCiAgICAKICAgICMgVXBkYXRlIHRpdGxlIHdpdGggdGhlIHJlbWFpbmluZyB0ZXh0IGFmdGVyIHRoZSBicmVhawogICAgdGl0bGUgPC0gc3Vic3RyKHRpdGxlLCBicmVha19hdCArIDEsIG5jaGFyKHRpdGxlKSkKICB9CiAgCiAgIyBBZGQgdGhlIHJlbWFpbmluZyBwYXJ0IG9mIHRoZSB0aXRsZQogIHdyYXBwZWRfdGl0bGUgPC0gcGFzdGUwKHdyYXBwZWRfdGl0bGUsIHRpdGxlKQogIAogIHJldHVybih3cmFwcGVkX3RpdGxlKQp9CmBgYAoKCmBgYHtyIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD04fQoKcGxvdGxpc3QgPC0gbGlzdCgpCgpmb3IgKHNpZyBpbiBuYW1lcyhkZl9zc0dTRUEpKXsKICAKICBkZl9zdWJzZXRfc3NHU0VBIDwtIGRmX3NzR1NFQVtbc2lnXV0KICBkZl9zdWJzZXRfbG9nbWVkaWFuIDwtIGRmX2xvZ21lZGlhbltbc2lnXV0KICAKICBkZl9zdWJzZXRfbWVyZ2UgPC0gbWVyZ2UoZGZfc3Vic2V0X3NzR1NFQSxkZl9zdWJzZXRfbG9nbWVkaWFuLGJ5PSJzYW1wbGUiKQogIAogICMgV3JhcCB0aGUgc2lnbmF0dXJlIG5hbWUgdXNpbmcgdGhlIGhlbHBlciBmdW5jdGlvbgogIHdyYXBwZWRfdGl0bGUgPC0gd3JhcF90aXRsZV9hdXgoc2lnLCB3aWR0aCA9IDIwKSAgCiAgCiAgcGxvdGxpc3RbW3NpZ11dIDwtIGdncGxvdDI6OmdncGxvdChkZl9zdWJzZXRfbWVyZ2UsIGFlcyh4PXNjb3JlLngsIHk9c2NvcmUueSkpICsKICAgIGdlb21fcG9pbnQoc2l6ZT00LCBhbHBoYT0wLjgsIGZpbGw9ImRhcmtncmV5Iiwgc2hhcGU9MjEpICsKICAgIHRoZW1lX2J3KCkgKwogICAgeGxhYigic3NHU0VBIEVucmljaG1lbnQgU2NvcmUiKSArCiAgICB5bGFiKCJOb3JtYWxpc2VkIFNpZ25hdHVyZSBTY29yZSIpICsKICAgIGdndGl0bGUod3JhcHBlZF90aXRsZSkgKwogICAgdGhlbWUocGxvdC50aXRsZSA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZT0xMCksCiAgICAgICAgICBwbG90LnN1YnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgCiAgCn0KCmdncHVicjo6Z2dhcnJhbmdlKHBsb3RsaXN0PXBsb3RsaXN0LCBucm93PTMsIG5jb2w9NCwgYWxpZ24gPSAiaCIpCmBgYAoKIyBEZWJ1Z2dpbmcKCiMjIEluZGl2aWR1YWwgR2VuZXMKCi0gUENBIHdpdGggZ2VuZXMgZnJvbSBzaWduYXR1cmVzIG9ubHkKCiMjIyBWaW9saW4gRXhwcmVzc2lvbiBQbG90cwoKYGBge3IgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KCgpzZW5lc2NlbmNlX3RyaWdnZXJzX2NvbG9ycyA8LSBjKAogICJub25lIiA9ICIjRTU3MzczIiwgICMgU29mdCByZWQgIAogICJSYWRpYXRpb24iID0gIiNCREJEQkQiLCAgIyBNZWRpdW0gZ3JheSAgCiAgIkROQSBkYW1hZ2UiID0gIiM2NEI1RjYiLCAgIyBCcmlnaHRlciBibHVlICAKICAiVGVsb21lcmUgc2hvcnRlbmluZyIgPSAiIzRGQzNGNyIsICAjIFZpdmlkIHNreSBibHVlICAKICAiRE5BIGRlbWV0aHlsYXRpb24iID0gIiNCQTY4QzgiLCAgIyBSaWNoIGxhdmVuZGVyICAKICAiT3hpZGF0aXZlIHN0cmVzcyIgPSAiI0ZERDgzNSIsICAjIFN0cm9uZyB5ZWxsb3cgIAogICJDb25kaXRpb25lZCBNZWRpdW0iID0gIiNGMjk5NEEiLCAgIyBXYXJtIG9yYW5nZSAgCiAgIk9uY29nZW5lIiA9ICIjODFDNzg0IiwgICMgTWVkaXVtIGdyZWVuICAKICAiTGlwaWQgQWNjdW11bGF0aW9uIiA9ICIjRTU3MzczIiwgICMgQ29yYWwgIAogICJDYWxjaXVtIGluZmx1eCIgPSAiIzI2QTY5QSIsICAjIERlZXAgdGVhbCAgCiAgIlBsYXNtYSBtZW1icmFuZSBkeXNydXB0aW9uIiA9ICIjRDMyRjJGIiwgICMgU3Ryb25nIHNhbG1vbiAgCiAgIk9TS00gZmFjdG9ycyIgPSAiI0ZGQjc0RCIsICAjIEJyaWdodCBwZWFjaCAgCiAgIllBUCBLTyIgPSAiIzk1NzVDRCIgICMgRGVlcCBwYXN0ZWwgcHVycGxlICAKKQoKCkluZGl2aWR1YWxHZW5lc19WaW9saW5zKGRhdGEgPSBjb3JyY291bnRzX21lcmdlLCBtZXRhZGF0YSA9IG1ldGFkYXRhX21lcmdlLCBnZW5lcyA9IGMoIkNES04xQSIsICJDREtOMkEiLCAiR0xCMSIsIlRQNTMiLCJDQ0wyIiksIEdyb3VwaW5nVmFyaWFibGUgPSAiQ29uZGl0aW9uIiwgcGxvdD1ULCBuY29sPU5VTEwsIG5yb3c9MiwgZGl2aWRlPSJDZWxsVHlwZSIsIGludmVydF9kaXZpZGU9RkFMU0UsQ29sb3JWYWx1ZXM9c2VuZXNjZW5jZV90cmlnZ2Vyc19jb2xvcnMsIHBvaW50U2l6ZT0yLCBDb2xvclZhcmlhYmxlPSJTZW5lc2NlbnRUeXBlIiwgdGl0bGU9IlNlbmVzY2VuY2UiLCB3aWR0aFRpdGxlPTE2LHlfbGltaXRzID0gTlVMTCxsZWdlbmRfbnJvdz00LCB4bGFiPSJDb25kaXRpb24iLGNvbG9ybGFiPSIiKSAKYGBgCgoKCiMjIyBDb3JyZWxhdGlvbiBIZWF0bWFwCgoKYGBge3IgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0Kb3B0aW9ucyhlcnJvcj1yZWNvdmVyKQpDb3JyZWxhdGlvbkhlYXRtYXAoZGF0YT1jb3JyY291bnRzX21lcmdlLCAKICAgICAgICAgICAgICAgICAgIG1ldGFkYXRhID0gbWV0YWRhdGFfbWVyZ2UsIAogICAgICAgICAgICAgICAgICAgZ2VuZXM9YygiQ0RLTjFBIiwgIkNES04yQSIsICJHTEIxIiwiVFA1MyIsIkNDTDIiKSwgCiAgICAgICAgICAgICAgICAgICBzZXBhcmF0ZS5ieSA9ICJDb25kaXRpb24iLCAKICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJwZWFyc29uIiwgIAogICAgICAgICAgICAgICAgICAgY29sb3JsaXN0ID0gbGlzdChsb3cgPSAiIzNGNDE5MyIsIG1pZCA9ICIjRjlGNEFFIiwgaGlnaCA9ICIjQjQ0MTQxIiksCiAgICAgICAgICAgICAgICAgICBsaW1pdHNfY29sb3JzY2FsZSA9IGMoLTEsMCwxKSwgCiAgICAgICAgICAgICAgICAgICB3aWR0aFRpdGxlID0gMTYsIAogICAgICAgICAgICAgICAgICAgdGl0bGUgPSAidGVzdCIsIAogICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgICBjbHVzdGVyX2NvbHVtbnMgPSBUUlVFLCAgCiAgICAgICAgICAgICAgICAgICBkZXRhaWxlZHJlc3VsdHMgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICBsZWdlbmRfcG9zaXRpb249InJpZ2h0IiwKICAgICAgICAgICAgICAgICAgIHRpdGxlc2l6ZT0yMCkKCgpgYGAKCgoKCiMjIyBFeHByZXNzaW9uIEhlYXRtYXBzCgpgYGB7ciBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NH0Kb3B0aW9ucyhlcnJvcj1yZWNvdmVyKQoKYW5ub3RhdGlvbl9jb2xvcnMgPC0gbGlzdCgKICBDZWxsVHlwZSA9IGMoCiAgICAiRmlicm9ibGFzdCIgICA9ICIjRkY2OTYxIiwgICAjIFN0cm9uZyBQYXN0ZWwgUmVkICAKICAgICJLZXJhdGlub2N5dGUiID0gIiNGRkIzNDciLCAgICMgU3Ryb25nIFBhc3RlbCBPcmFuZ2UgIAogICAgIk1lbGFub2N5dGUiICAgPSAiI0ZGRDcwMCIsICAgIyBTdHJvbmcgUGFzdGVsIFllbGxvdyAgCiAgICAiRW5kb3RoZWxpYWwiICA9ICIjNzdERDc3IiwgICAjIFN0cm9uZyBQYXN0ZWwgR3JlZW4gIAogICAgIk5ldXJvbmFsIiAgICAgPSAiIzc3OUVDQiIsICAgIyBTdHJvbmcgUGFzdGVsIEJsdWUgIAogICAgIk1lc2VuY2h5bWFsIiAgPSAiI0MyN0JBMCIgICAgIyBTdHJvbmcgUGFzdGVsIFB1cnBsZSAgCiAgKSwKICBDb25kaXRpb24gPSBjKAogICAgIlNlbmVzY2VudCIgICAgID0gIiM2NUFDN0MiLCAgIyBFeGFtcGxlIGNvbG9yOiBncmVlbmlzaAogICAgIlByb2xpZmVyYXRpdmUiID0gIiM1RjkwRDQiLCAgIyBFeGFtcGxlIGNvbG9yOiBibHVlaXNoCiAgICAiUXVpZXNjZW50IiAgICAgPSAiI0VEQTAzRSIgICAjIEV4YW1wbGUgY29sb3I6IG9yYW5nZQogICkKKQoKRXhwcmVzc2lvbkhlYXRtYXAoZGF0YT1jb3JyY291bnRzX21lcmdlLCAKICAgICAgICAgICAgICAgICAgbWV0YWRhdGEgPSBtZXRhZGF0YV9tZXJnZSwgCiAgICAgICAgICAgICAgICAgIGdlbmVzPWMoIkNES04xQSIsICJDREtOMkEiLCAiR0xCMSIsIlRQNTMiLCJDQ0wyIiksICAKICAgICAgICAgICAgICAgICAgYW5ub3RhdGUuYnkgPSBjKCJDZWxsVHlwZSIsIkNvbmRpdGlvbiIpLAogICAgICAgICAgICAgICAgICBhbm5vdGF0aW9uX2NvbG9ycyA9IGFubm90YXRpb25fY29sb3JzLAogICAgICAgICAgICAgICAgICBjb2xvcmxpc3QgPSBsaXN0KGxvdyA9ICIjM0Y0MTkzIiwgbWlkID0gIiNGOUY0QUUiLCBoaWdoID0gIiNCNDQxNDEiKSwKICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfY29sdW1ucyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICB0aXRsZSA9ICJ0ZXN0IiwgCiAgICAgICAgICAgICAgICAgIHRpdGxlc2l6ZSA9IDIwLAogICAgICAgICAgICAgICAgICBsZWdlbmRfcG9zaXRpb24gPSAicmlnaHQiLAogICAgICAgICAgICAgICAgICBzY2FsZV9wb3NpdGlvbj0icmlnaHQiKQoKYGBgCgoKCiMjIyBST0MvQVVDIAoKYGBge3IgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTR9CgpjZWxsVHlwZXNfY29sb3JzIDwtIGMoCiAgIkZpYnJvYmxhc3QiID0gIiNGRjY5NjEiLCAgICMgU3Ryb25nIFBhc3RlbCBSZWQgIAogICJLZXJhdGlub2N5dGUiID0gIiNGRkIzNDciLCAjIFN0cm9uZyBQYXN0ZWwgT3JhbmdlICAKICAiTWVsYW5vY3l0ZSIgPSAiI0ZGRDcwMCIsICAgIyBTdHJvbmcgUGFzdGVsIFllbGxvdyAgCiAgIkVuZG90aGVsaWFsIiA9ICIjNzdERDc3IiwgICMgU3Ryb25nIFBhc3RlbCBHcmVlbiAgCiAgIk5ldXJvbmFsIiA9ICIjNzc5RUNCIiwgICAgICMgU3Ryb25nIFBhc3RlbCBCbHVlICAKICAiTWVzZW5jaHltYWwiID0gIiNDMjdCQTAiICAgIyBTdHJvbmcgUGFzdGVsIFB1cnBsZSAgCikKClJPQ2FuZEFVQ3Bsb3QoY29ycmNvdW50c19tZXJnZSwgCiAgICAgICAgICAgICAgbWV0YWRhdGFfbWVyZ2UsIAogICAgICAgICAgICAgIGNvbmRpdGlvbl92YXIgPSAiQ29uZGl0aW9uIiwgCiAgICAgICAgICAgICAgY2xhc3MgPSAiU2VuZXNjZW50IiwgCiAgICAgICAgICAgICAgZ2VuZXM9YygiQ0RLTjFBIiwgIkNES04yQSIsICJHTEIxIiwiVFA1MyIsIkNDTDIiKSwgCiAgICAgICAgICAgICAgZ3JvdXBfdmFyPSJDZWxsVHlwZSIsCiAgICAgICAgICAgICAgcGxvdF90eXBlID0gImFsbCIsCiAgICAgICAgICAgICAgaGVhdG1hcF9wYXJhbXMgPSBsaXN0KGNvbCA9IGxpc3QoICIjRjlGNEFFIiAsIiNCNDQxNDEiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLjUsMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfcm93cz1UKSwKICAgICAgICAgICAgICByb2NfcGFyYW1zID0gbGlzdChucm93PTIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbD0yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9ycz1jZWxsVHlwZXNfY29sb3JzKSwKICAgICAgICAgICAgICBjb21tb21wbG90X3BhcmFtcyA9IGxpc3Qod2lkdGhzPWMoMC41LDAuNSkpKQoKCmBgYAoKIyMjIENvaGVuJ3MgZAoKYGBge3J9CkNvaGVuREhlYXRtYXAoY29ycmNvdW50c19tZXJnZSwgCiAgICAgICAgICAgICAgbWV0YWRhdGFfbWVyZ2UsIAogICAgICAgICAgICAgIGdlbmVzPWMoIkNES04xQSIsICJDREtOMkEiLCAiR0xCMSIsIlRQNTMiLCJDQ0wyIiksCiAgICAgICAgICAgICAgY29uZGl0aW9uX3ZhciA9ICJDb25kaXRpb24iLCAKICAgICAgICAgICAgICBjbGFzcyA9ICJTZW5lc2NlbnQiLCAKICAgICAgICAgICAgICBncm91cF92YXIgPSAiQ2VsbFR5cGUiLAogICAgICAgICAgICAgIHRpdGxlID0gTlVMTCwKICAgICAgICAgICAgICB3aWR0aFRpdGxlID0gMTYsCiAgICAgICAgICAgICAgaGVhdG1hcF9wYXJhbXMgPSBsaXN0KGNvbCA9IGxpc3QoICIjRjlGNEFFIiAsIiNCNDQxNDEiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gTlVMTCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzPVQpKQpgYGAKCiMjIyBQQ0Egd2l0aCBnZW5lcyBmcm9tIHNpZ25hdHVyZSBvbmx5CgpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQoKICBDZWxsVHlwZWNvbHMgPSBjKAogICAgIkZpYnJvYmxhc3QiICAgPSAiI0ZGNjk2MSIsICAgIyBTdHJvbmcgUGFzdGVsIFJlZCAgCiAgICAiS2VyYXRpbm9jeXRlIiA9ICIjRkZCMzQ3IiwgICAjIFN0cm9uZyBQYXN0ZWwgT3JhbmdlICAKICAgICJNZWxhbm9jeXRlIiAgID0gIiNGRkQ3MDAiLCAgICMgU3Ryb25nIFBhc3RlbCBZZWxsb3cgIAogICAgIkVuZG90aGVsaWFsIiAgPSAiIzc3REQ3NyIsICAgIyBTdHJvbmcgUGFzdGVsIEdyZWVuICAKICAgICJOZXVyb25hbCIgICAgID0gIiM3NzlFQ0IiLCAgICMgU3Ryb25nIFBhc3RlbCBCbHVlICAKICAgICJNZXNlbmNoeW1hbCIgID0gIiNDMjdCQTAiICAgICMgU3Ryb25nIFBhc3RlbCBQdXJwbGUgIAogICkKCnNlbmNvbHMgPC0gYygKICAiU2VuZXNjZW50IiA9ICIjRDMyRjJGIiwgICMgU3Ryb25nIHNhbG1vbiAgCiAgIlF1aWVzY2VudCIgPSAiI0ZGQjc0RCIsICAjIEJyaWdodCBwZWFjaCAgCiAgIlByb2xpZmVyYXRpdmUiID0gIiM5NTc1Q0QiICAjIERlZXAgcGFzdGVsIHB1cnBsZSAgCikKIApwbG90UENBKGRhdGE9Y29ycmNvdW50c19tZXJnZSwgCiAgICAgICAgbWV0YWRhdGE9bWV0YWRhdGFfbWVyZ2UsIAogICAgICAgIGdlbmVzPWMoIkNES04xQSIsICJDREtOMkEiLCAiR0xCMSIsIlRQNTMiLCJDQ0wyIiksIAogICAgICAgIHNjYWxlPUZBTFNFLCAKICAgICAgICBjZW50ZXI9VFJVRSwgCiAgICAgICAgUENzPWxpc3QoYygxLDIpLCBjKDIsMyksIGMoMyw0KSksIAogICAgICAgIENvbG9yVmFyaWFibGU9IkNvbmRpdGlvbiIsCiAgICAgICAgQ29sb3JWYWx1ZXM9c2VuY29scywKICAgICAgICBwb2ludFNpemU9NSwKICAgICAgICBsZWdlbmRfbnJvdz0xLCAKICAgICAgICBuY29sPTMsIAogICAgICAgIG5yb3c9TlVMTCkKYGBgCgpgYGB7cn0KCiMnIEBpbXBvcnRGcm9tIGVkZ2VSIERHRUxpc3QKIycgQGltcG9ydEZyb20gc3RhdHMgcHJjb21wCiMnIEBpbXBvcnQgZ2dwbG90MgojJyBAaW1wb3J0RnJvbSBnZ3B1YnIgZ2dhcnJhbmdlCgoKYGBgCgoKCg==